package ar.com.mooral.crohn;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;


import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;
import android.widget.TextView;
import ar.com.mooral.crohn.application.CrohnContext;
import ar.com.mooral.data.*;
import ar.com.mooral.pojo.BasePojo;

public class Synchronizer {
	
	static final String synchronizeUrl = "http://82.223.107.224:8080/ServidorCrohnAlDia/synchronizeDataAndroid.action";
	static final String putDataUrl = "http://82.223.107.224:8080/ServidorCrohnAlDia/putDataAndroid.action";
	static final String registerPatientUrl = "http://82.223.107.224:8080/ServidorCrohnAlDia/registroPaciente.action";
	static final String validateCodeUrl = "http://82.223.107.224:8080/ServidorCrohnAlDia/validaCodigo.action";
	static final String loginUrl = "http://82.223.107.224:8080/ServidorCrohnAlDia/loginUsuario.action";
	
	private Context context;
	private SharedPreferences settings;
	
	private Hashtable<String, String> errors;


	public Synchronizer(Context context) {
		this.context = context;
		this.settings = context.getSharedPreferences(Crohn.PREFS_NAME, 0);

		this.errors = new Hashtable<String, String>();
		this.errors.put("-1", "5|Error General");
		this.errors.put("-2", "3|Cuenta Paciente Inactiva");
		this.errors.put("-3", "3|Cuenta Medico Inactiva");
		this.errors.put("-4", "3|Usuario Invalido");
		this.errors.put("-5", "3|Datos Login Medico Inválidos");
		this.errors.put("-6", "3|Datos Login Paciente Inválidos");
		this.errors.put("-7", "2|Error Registro Medico");
		this.errors.put("-8", "2|Error Registro Paciente");
		this.errors.put("-9", "1|Registro Medico CAMPO");
		this.errors.put("-10", "5|Registro Paciente CAMPO");
		
	}


/**
 * Send each updates 
 * 	                     
 */
	public void receiveUpdates() {
		Document doc = this.doPost(synchronizeUrl, this.getReceiveXml());
		this.updateRecords(doc, "receive");
	}	
	
	private void updateRecords(Document doc, String operation) {
		
		/* Save new dated received from the server */
		NodeList nodeList = doc.getElementsByTagName("fecha");	
		String lastUpdate = nodeList.item(0).getChildNodes().item(0).getNodeValue();
		
		
		/* Save last update date */
		SharedPreferences.Editor editor = this.settings.edit();
		editor.putString("lastUpdate", lastUpdate);
		editor.commit();
		

		//Synchronizer.getXmlStringFromXmlDocument(doc)
		/* Check for the updates */
		
		String types[];
		if (operation.equals("put")) {
			types = new String[] {"altas"};
		} else {
			types = new String[] {"altas", "modificaciones", "bajas"};
		}
		
		DatabaseHelper dbs[] = this.getAllDatabasesHelperInstances();
		
	    for (int typeId = 0; typeId < types.length; typeId++) {


			NodeList nodes = doc.getElementsByTagName(types[typeId]);
			if (nodes.getLength() == 1) {
				nodes = nodes.item(0).getChildNodes();
			} else {
				continue;
			}


			for (int i = 0; i < nodes.getLength(); i++) {
	
				String nodeShortName = nodes.item(i).getNodeName();
				
				/*Because of Carlos bug
				if (types[typeId].equals("altas") && nodeShortName.equals("PAC")) {
					continue;
				}
				*/
				
				NodeList lalList = (NodeList) nodes.item(i).getChildNodes();
	
				for (int j = 0; j < lalList.getLength(); j++) {

					Node r = (Node) lalList.item(j).getFirstChild();
					Hashtable<String, String> data = new Hashtable<String, String>();

					while (r != null) {
						
						String nodeName = r.getNodeName();
						
						if (operation.equals("put")) {
							if (nodeName.equals("id_local")) {
								data.put("id", r.getFirstChild().getNodeValue());
							} else if (nodeName.equals("id")) {
								data.put("remoteId", r.getFirstChild().getNodeValue());
							} else {
								data.put(nodeName, r.getFirstChild().getNodeValue());
							}
						} else {
							if (nodeName.equals("id")) {
								data.put("remoteId", r.getFirstChild().getNodeValue());
							} else if (nodeName.equals("fecha") && !nodeShortName.equals("PAC")) {
								data.put(nodeName, DatabaseHelper.getFrenchDate(r.getFirstChild().getNodeValue()));
							} else {
								data.put(nodeName, r.getFirstChild().getNodeValue());
							}
						}

						r = r.getNextSibling();
					}

					
					if (operation.equals("put")) {
						for (int k = 0; k < dbs.length; k++) {
							if (dbs[k].getShortTableName().equals(nodeShortName)) {
								dbs[k].update(data, false);
								break;
							}
						}
					} else {
						if (types[typeId].equals("altas")) {
							for (int k = 0; k < dbs.length; k++) {
								if (dbs[k].getShortTableName().equals(nodeShortName)) {
									dbs[k].insert(data, false);
									break;
								}
							}
						} else if (types[typeId].equals("modificaciones")) {
							for (int k = 0; k < dbs.length; k++) {
								if (dbs[k].getShortTableName().equals(nodeShortName)) {
									String id = dbs[k].getLocalId(data.get("remoteId"));
									data.put("id", id);
									data.put("remoteId", id); // TODO CAMBIO EN LA SINCRONIZACION 
									dbs[k].update(data, false);
									break;
								}
							}
						} else if (types[typeId].equals("bajas")) {
							for (int k = 0; k < dbs.length; k++) {
								if (dbs[k].getShortTableName().equals(nodeShortName)) {
									String id = dbs[k].getLocalId(data.get("remoteId"));
									dbs[k].delete(id, false);
									break;
								}
							}
						}
					}
				}
				
			}
	    }
			
	}
	
/**
 * Performs a http post
 *  	
 * @param url
 * @param data
 * @return xml Document
 */
	
	private Document doPost(String url, String data) {

	    HttpClient httpclient = new DefaultHttpClient();
	    HttpPost httppost = new HttpPost(url);

	    Document doc = null;
	    
	    try {
	    	
	    	/* Create Post Request */
	    	StringEntity se = new StringEntity(data);
	    	se.setContentType("text/xml");
	    	httppost.setHeader("Content-Type","text/xml");
	    	httppost.setEntity(se);
	    	
	        /* Execute HTTP Post Request */
	    	String response = httpclient.execute(httppost, new BasicResponseHandler());

	        /* Create a xml dom document */
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();

			doc = db.parse(new InputSource(new StringReader(response)));
			doc.getDocumentElement().normalize();

	    } catch (ClientProtocolException e) {
	        Log.v("CROHN", "ClientProtocolException: " + e.getMessage());
	    } catch (IOException e) {
	    	Log.v("CROHN", "IOException: " + e.getMessage());
	    } catch (ParserConfigurationException e) {
	    	Log.v("CROHN", "ParserConfigurationException: " + e.getMessage());
		} catch (SAXException e) {
			Log.v("CROHN", "SAXException: " + e.getMessage());
		}
	    
	    return doc;
	}



/**
 * Creates xml for asking for new info since last update
 * 
 * @return String
 */
	private String getReceiveXml() {
		
		String sXml = "";
		
		try {
		    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();  
		    DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();  
		    Document document = documentBuilder.newDocument();
		    
		    Element rootElement = document.createElement("datos");  
		    document.appendChild(rootElement);
		    
		    this.addLoginNode(document, rootElement);
		    
		    sXml = Synchronizer.getXmlStringFromXmlDocument(document);
		    
		} catch (Exception e) {
			Log.v("CROHN", "Exception: " + e.getMessage());
		}

		return sXml;
	}


	private int getPutNode(Document document, Element parentElement, String nodeName, Vector <Hashtable <String, String>> data, String operationType) {
		
	    if (data.size() > 0) {

		    Element nodeElement = document.createElement(nodeName);  
		    parentElement.appendChild(nodeElement);
	    	
		    Iterator<Hashtable<String, String>> itr = data.iterator();
		    while (itr.hasNext()) {

		    	Hashtable<String, String> record = itr.next();

			    Element recordElement = document.createElement("registro");  
			    nodeElement.appendChild(recordElement);
		    	
			    //Enumeration<String> e = record.keys();

		    	Iterator i = ((BasePojo)record).getDatabaseHelper().getFields().getFieldNames().iterator();
		    	while(i.hasNext()) {
		    		
			    //while (e.hasMoreElements()) {
			    	String key = (String) i.next(); //e.nextElement();
			      	String value = String.valueOf(record.get(key)).trim();
			      	
			      	// TODO CAMBIO EN LA SINCRONIZACION
			      	if (operationType.equals("altas")) { 			      		
				      	if (key.equals("id")) {
				      		key = "id_local"; 
				      	}
			      	} else {
			      		
				      	if (key.equals("id")) {
				      		//continue;
				      		value = String.valueOf(record.get("remoteId")).trim();
				      	}

			      	}
			      	
			      	
			      	
			      	if (value.length() > 0) {
					    Element fieldElement = document.createElement(key);
					    fieldElement.appendChild(document.createTextNode(value));
					    recordElement.appendChild(fieldElement);
			      	}
			    }

		    }
		    
		    return 1;
	    } else {
	    	return 0;
	    }
	}


	private void sendUpdates(DatabaseHelper dbs[]) {
		
		// Before sending local changes, must always receive data. 
		this.receiveUpdates();
		Document doc = this.doPost(putDataUrl, this.getPutXml(dbs));
		//Synchronizer.getXmlStringFromXmlDocument(doc)
		

		// search for errors
		NodeList nodeList = doc.getElementsByTagName("error");
		if (nodeList.getLength() == 0) {
		
			this.updateRecords(doc, "put");		

			// mark records as sended
			Updater updater = new Updater(this.context);
			updater.doPostPutUpdates();

		} else {
			// try again latter
		}
		
	}
	
	public void sendUpdates(DatabaseHelper db) {
		this.sendUpdates(new DatabaseHelper[] {db});
	}

	public void sendUpdates() {
		DatabaseHelper dbs[] = this.getAllDatabasesHelperInstances();
		this.sendUpdates(dbs);
	}
	

	public DatabaseHelper[] getAllDatabasesHelperInstances() {
		return new DatabaseHelper[] {
				new Consultations(this.context),
				new Depositions(this.context),
				new Doctors(this.context),
				new FoodList(this.context),
				new Foods(this.context),
				new GeneralStatus(this.context),
				new Medications(this.context),
				new MedicationsList(this.context),
				new MissedMedicationReason(this.context),
				new MissedMedications(this.context),
				new Pain(this.context),
				new Patients(this.context),
				new PatientSymptoms(this.context),
				new SexualActivityIncidence(this.context),
				new SocialActivityIncidence(this.context),
				new Symptoms(this.context),
				new WorkActivityIncidence(this.context)
			};
	}
	
/**
 * Creates xml to be sended with local generated data since last update
 * 
 * @return String
 */
	private String getPutXml(DatabaseHelper dbs[]) {
		
		String sXml = "";
		
		try {
			
		    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();  
		    DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();  
		    Document document = documentBuilder.newDocument();
		    
		    Element rootElement = document.createElement("datos");
		    document.appendChild(rootElement);
		    
		    this.addLoginNode(document, rootElement);


		    Element addElement = document.createElement("altas");  
		    int adds = 0;
		    
		    Element editElement = document.createElement("modificaciones");  
		    int edits = 0;

		    Element deleteElement = document.createElement("bajas");
		    int deletes = 0;
		    
		    for (int i = 0; i < dbs.length; i++) {
		    	if (!dbs[i].getShortTableName().equals("MMP")) { // do not sync motivoMedicacionPerdida
		    		adds += this.getPutNode(document, addElement, dbs[i].getShortTableName(), dbs[i].findAllAdds(), "altas");
		    		edits += this.getPutNode(document, editElement, dbs[i].getShortTableName(), dbs[i].findAllEdits(), "modificaciones");
		    		deletes += this.getPutNode(document, deleteElement, dbs[i].getShortTableName(), dbs[i].findAllDeletes(), "bajas");
		    	}
		    }
		    
		    if (adds > 0) {
			    rootElement.appendChild(addElement);
		    }
		    if (edits > 0) {
			    rootElement.appendChild(editElement);
		    }
		    if (deletes > 0) {
			    rootElement.appendChild(deleteElement);
		    }
		    
		    sXml = Synchronizer.getXmlStringFromXmlDocument(document);
		    
		} catch (Exception e) {
			Log.v("CROHN", "Exception: " + e.getMessage());
		}

		return sXml;
	}
	

	
/**
 * Creates string out from xml document
 * 
 * @return String
	public static String getXmlStringFromXmlDocument(Document root) throws IOException {

        StringBuilder result = new StringBuilder();

        if (root.getNodeType() == 3)
            result.append(root.getNodeValue());
        else {
            if (root.getNodeType() != 9) {
                StringBuffer attrs = new StringBuffer();
                for (int k = 0; k < root.getAttributes().getLength(); ++k) {
                    attrs.append(" ").append(
                            root.getAttributes().item(k).getNodeName()).append(
                            "=\"").append(
                            root.getAttributes().item(k).getNodeValue())
                            .append("\" ");
                }
                result.append("<").append(root.getNodeName()).append(" ")
                        .append(attrs).append(">");
            } else {
                result.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            }

            NodeList nodes = root.getChildNodes();
            for (int i = 0, j = nodes.getLength(); i < j; i++) {
                Node node = nodes.item(i);
                result.append(getXmlStringFromXmlDocument((Document) node));
            }

            if (root.getNodeType() != 9) {
                result.append("</").append(root.getNodeName()).append(">");
            }
        }
        return result.toString();
    }
 */
	
	
	public static String getXmlStringFromXmlDocument(Document document) {
		
		String sXml = "";
		
		try {
			
			
			//document.to
			
		    TransformerFactory transfac = TransformerFactory.newInstance();
		    Transformer trans = transfac.newTransformer();
		    StringWriter sw = new StringWriter();
		    StreamResult result = new StreamResult(sw);
		    DOMSource source = new DOMSource(document);
			trans.transform(source, result);
			sXml = sw.toString();
			
		} catch (TransformerConfigurationException e) {
			Log.v("CROHN", "TransformerConfigurationException: " + e.getMessage());
		} catch (TransformerException e) {
			Log.v("CROHN", "TransformerException: " + e.getMessage());
		}
		
		return sXml;
	}
	

/**
 * Creates login xml node
 * 
 * @return void
 */
	private void addLoginNode(Document document, Element rootElement) {
		
	    Element eLogin = document.createElement("login");  
	    rootElement.appendChild(eLogin);
	    
	    Element eUser = document.createElement("usuario");
	    eUser.appendChild(document.createTextNode(this.settings.getString("username", "")));
	    eLogin.appendChild(eUser);

	    Element ePassword = document.createElement("password");
	    ePassword.appendChild(document.createTextNode(this.settings.getString("password", "")));
	    eLogin.appendChild(ePassword);
	    
	    Element eDate = document.createElement("fecha");
	    eDate.appendChild(document.createTextNode(this.settings.getString("lastUpdate", "2011-01-01 00:00:00")));
	    eLogin.appendChild(eDate);
		    
	}

	
/**
 * Verifies suplied username and password vs server info.
 * 	
 * 
 */
	public Result doLogin(String username, String password) {

		String response = this.doHttpGet(loginUrl + "?usuario=" + username + "&password=" + password).replaceAll("&tipoUsuario=-?[0-9]+", "");
		
		Result r = new Result(Long.parseLong(response));

		if (!r.hasError()) {

			/* Save patient code */
			SharedPreferences.Editor editor = this.settings.edit();
			editor.putString("username", username);
			editor.putString("password", password);
			editor.putString("patientCode", response);
			editor.commit();
			
	        CrohnContext.setPassword(password);
	        CrohnContext.setUsername(username);
	        CrohnContext.setPatientCode(response);
			
		} else {
			
			String desc;
			try {
				desc = this.errors.get(response);
			} catch (Exception e) {
				desc = "El usuario o clave ingresado es invalido";
			}

			
			r.setType(Result.CRITICAL);
			r.setTitle("Validacion de Clave");
			r.setDescription(desc);
		}
		
		return r;
	}
	

/**
 * Validate code
 * 	
 */
	public Result validateCode(String code) {

		String response = this.doHttpGet(validateCodeUrl + "?codigo=" + code);
		response = response.replace("tipoRegistro=", "");
		Result r = new Result(Long.parseLong(response));

		if (r.hasError() || !response.equals("1")) {
			
			String desc;
			try {
				desc = this.errors.get(response);
			} catch (Exception e) {
				desc = "El codigo ingresado no es correcto o no corresponde a un medico";
			}

			r.setType(Result.CRITICAL);
			r.setTitle("Validacion de Codigo");
			r.setDescription(desc);
		} else {
			CrohnContext.setMedicalCode(code);
		}
		
		return r;
	}	
		
	
/**
 * Register patient
 * 	
 */
	public String registerPatient(Hashtable<String, String> data) {
		
		String username = "";
		String password = "";
		
		String[] args = new String[data.size()];
		Enumeration<String> e = data.keys();
        int i = 0;
	    while (e.hasMoreElements()) {
	    	String key = (String) e.nextElement();
	    	args[i++] = key + "=" + (String) data.get(key);
	    	
	    	if (key == "usuario") {
	    		username = data.get(key);
	    	} else if (key == "password") {
	    		password = data.get(key);
	    	}
	    }
	
	    
	    Log.v("CROHN", registerPatientUrl + "?" + TextUtils.join("&", args));
		String response = this.doHttpGet(registerPatientUrl + "?" + TextUtils.join("&", args));
			
		// if there where no errors
		if (Integer.parseInt(response) > 0) {
			
			/* Save user data */
			SharedPreferences.Editor editor = this.settings.edit();
			editor.putString("username", username);
			editor.putString("password", password);
			editor.putString("patientCode", response);
			editor.commit();
			
	        CrohnContext.setPassword(password);
	        CrohnContext.setUsername(username);
	        CrohnContext.setPatientCode(response);
			
		} else {
			response = this.errors.get(response);
		}
			
		return response; 
	}
	

	
	private String doHttpGet(String url) {
		
		String response = "";
		
	    try {
	    	
		    HttpClient httpclient = new DefaultHttpClient();
		    HttpGet httpget = new HttpGet(url);
			response = httpclient.execute(httpget, new BasicResponseHandler()).replace("respuesta=", "");

			
		} catch (ClientProtocolException ex) {
			Log.v("CROHN", "ClientProtocolException: " + ex.getMessage());
		} catch (IOException ex) {
			Log.v("CROHN", "IOException: " + ex.getMessage());
		}

		return response; 
	}
	

}